﻿using System;
using System.Threading;
using System.Threading.Tasks;

namespace Zero.WebAPI.Framework.Helper.ID
{
    public class DriftSnowflakeIdGenerator
    {
        private const long Twepoch = 1288834974657L;

        private const int WorkerIdBits = 5;
        private const int DatacenterIdBits = 5;
        private const int SequenceBits = 12;

        /*
            负数求原码：
                其他位正常计算，符号位置1
                    -18 的原码 = 1 0010010 = 10010010

            负数补码计算方式： 求 -18 的补码（二进制表示）
                1.求正数的二进制
                    +18 => 00010010
                2.按位取反（0变1，1变0） 
                    00010010 → 11101101
                3.+1（得到补码）
                    11101101 + 1 = 11101110
            负数补码恢复到原值：（与上面类似）按位取反，末位加一
         */
        private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);         // 0b11111 = 31  2^5
        private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits); // 0b11111 = 31  2^5
        private const long SequenceMask = -1L ^ (-1L << SequenceBits);        // 0b111111111111 = 4095  2^12

        private const int WorkerIdShift = SequenceBits;
        private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
        private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;

        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

        private long _lastTimestamp = -1L;
        private long _sequence = 0L;

        public long WorkerId { get; private set; }
        public long DatacenterId { get; private set; }

        public DriftSnowflakeIdGenerator(long workerId, long datacenterId)
        {
            if (workerId > MaxWorkerId || workerId < 0)
                throw new ArgumentException($"机器的ID号必须在 0 到 {MaxWorkerId} 之间");

            if (datacenterId > MaxDatacenterId || datacenterId < 0)
                throw new ArgumentException($"数据中心的ID号必须在 0 到 {MaxDatacenterId} 之间");

            WorkerId = workerId;
            DatacenterId = datacenterId;
        }

        public long NextId()
        {
            _semaphore.Wait();

            try
            {
                var timestamp = GetCurrentTimeMillis();

                if (timestamp < _lastTimestamp)
                {
                    // 时钟回拨微调，出现时钟回拨时先不生成ID，等到当前时间戳超过旧的时间戳时再继续生成ID
                    var offset = _lastTimestamp - timestamp;
                    if (offset <= 5)
                    {
                        Task.Delay((int)(offset * 2)).Wait();
                        timestamp = GetCurrentTimeMillis();

                        if (timestamp < _lastTimestamp)
                            throw new Exception($"时钟回拨.  {offset}毫米内拒绝继续生成ID");
                    }
                    else
                    {
                        throw new Exception($"时钟严重回拨. {offset}毫米内拒绝继续生成ID");
                    }
                }

                if (_lastTimestamp == timestamp)    // 同一毫秒
                {
                    _sequence = (_sequence + 1) & SequenceMask;
                    // Id递增数量超过4095后毫秒数加1
                    if (_sequence == 0)
                    {
                        timestamp = WaitNextMillisAsync(_lastTimestamp).Result;
                    }
                }
                else // 当前时间戳大于旧的时间戳，则序列号重新从0开始计数
                {
                    _sequence = 0L;
                }

                _lastTimestamp = timestamp;

                return ((timestamp - Twepoch) << TimestampLeftShift)
                       | (DatacenterId << DatacenterIdShift)
                       | (WorkerId << WorkerIdShift)
                       | _sequence;
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public async Task<long> NextIdAsync()
        {
            await _semaphore.WaitAsync();

            try
            {
                var timestamp = GetCurrentTimeMillis();

                if (timestamp < _lastTimestamp)
                {
                    var offset = _lastTimestamp - timestamp;
                    if (offset <= 5)
                    {
                        await Task.Delay((int)(offset * 2));
                        timestamp = GetCurrentTimeMillis();

                        if (timestamp < _lastTimestamp)
                            throw new Exception($"时钟回拨.  {offset}毫米内拒绝继续生成ID");
                    }
                    else
                    {
                        throw new Exception($"时钟严重回拨. {offset}毫米内拒绝继续生成ID");
                    }
                }

                if (_lastTimestamp == timestamp)
                {
                    _sequence = (_sequence + 1) & SequenceMask;
                    if (_sequence == 0)
                    {
                        timestamp = await WaitNextMillisAsync(_lastTimestamp);
                    }
                }
                else
                {
                    _sequence = 0L;
                }

                _lastTimestamp = timestamp;

                return ((timestamp - Twepoch) << TimestampLeftShift)
                       | (DatacenterId << DatacenterIdShift)
                       | (WorkerId << WorkerIdShift)
                       | _sequence;
            }
            finally
            {
                _semaphore.Release();
            }
        }

        private async Task<long> WaitNextMillisAsync(long lastTimestamp)
        {
            long timestamp = GetCurrentTimeMillis();
            while (timestamp <= lastTimestamp)
            {
                await Task.Yield();     // 当有其他线程分配到cpu时间片后，任务调度器将cpu的执行权从当前线程让给其他线程
                timestamp = GetCurrentTimeMillis();
            }
            return timestamp;
        }

        private long GetCurrentTimeMillis()
        {
            return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        }
    }

}
